/*
 * Copyright (c) 2018, apexes.net. All rights reserved.
 *
 *         http://www.apexes.net
 *
 */
package net.apexes.commons.querydsl;

import com.querydsl.core.Tuple;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.MappingProjection;
import com.querydsl.sql.SQLQuery;

import java.util.List;

/**
 *
 * @author <a href=mailto:hedyn@foxmail.com>HeDYn</a>
 */
public final class Mappings {

    public <T> MappingBuilder<T> of(Class<T> classType, Expression<?>... expressions) {
        return new MappingBuilder<>(classType, expressions);
    }

    public static MappingSQLBuilder of(SQLQuery<?> sqlQuery, Expression<?>... expressions) {
        return new MappingSQLBuilder(sqlQuery).columns(expressions);
    }

    public static MappingSQLBuilder of(SQLQuery<?> sqlQuery) {
        return new MappingSQLBuilder(sqlQuery);
    }

    /**
     *
     * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
     * @param <T>
     */
    public static class MappingBuilder<T> {

        private final Class<T> classType;
        private final Expression<?>[] expressions;

        private MappingBuilder(Class<T> classType, Expression<?>... expressions) {
            this.classType = classType;
            this.expressions = expressions;
        }

        public MappingProjection<T> mapping(Mapping<T> mapping) {
            return new ProjectionAdapter<>(mapping, classType, expressions);
        }
    }

    /**
     *
     * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
     */
    public static class MappingSQLBuilder {

        private final SQLQuery<?> sqlQuery;
        private Expression<?>[] expressions;

        private MappingSQLBuilder(SQLQuery<?> sqlQuery) {
            this.sqlQuery = sqlQuery;
        }

        public MappingSQLBuilder columns(Expression<?>... expressions) {
            this.expressions = expressions;
            return this;
        }

        public void fetch(VoidMapping mapping) {
            sqlQuery.select(new ProjectionAdapter<>(new MappingAdapter(mapping), Void.class, expressions)).fetch();
        }

        public <T> List<T> fetch(Class<T> classType, Mapping<T> mapping) {
            return sqlQuery.select(mapping(classType, mapping)).fetch();
        }

        public <T> T fetchFirst(Class<T> classType, Mapping<T> mapping) {
            return sqlQuery.select(mapping(classType, mapping)).fetchFirst();
        }

        public <T> T fetchOne(Class<T> classType, Mapping<T> mapping) {
            return sqlQuery.select(mapping(classType, mapping)).fetchOne();
        }

        private <T> MappingProjection<T> mapping(Class<T> classType, Mapping<T> mapping) {
            return new ProjectionAdapter<>(mapping, classType, expressions);
        }

    }

    /**
     *
     * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
     *
     * @param <T>
     */
    private static class ProjectionAdapter<T> extends MappingProjection<T> {
        private static final long serialVersionUID = 1L;

        private final Mapping<T> mapping;

        public ProjectionAdapter(Mapping<T> mapping, Class<T> classType, Expression<?>... expressions) {
            super(classType, expressions);
            this.mapping = mapping;
        }

        @Override
        protected T map(Tuple row) {
            return mapping.map(row);
        }
    }

    /**
     *
     * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
     */
    private static class MappingAdapter implements Mapping<Void> {

        private final VoidMapping mapping;

        public MappingAdapter(VoidMapping mapping) {
            this.mapping = mapping;
        }

        @Override
        public Void map(Tuple row) {
            mapping.map(row);
            return null;
        }
    }

    /**
     *
     * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
     *
     * @param <T>
     */
    public interface Mapping<T> {

        T map(Tuple row);
    }

    /**
     *
     * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
     */
    public interface VoidMapping {

        void map(Tuple row);
    }
}
